//
//  ASSessionManager.m
//  AnisetteData
//
//  Created by 吴双 on 2023/1/23.
//

#import "ASSessionManager.h"
#import <AltSign/AltSign.h>
#import "ASSession+Private.h"
#import "ASSession+Team.h"
#import "ASError.h"
#import "ASAnisetteDataProvider.h"
#import "ASType.h"
#import <openssl/evp.h>
#import "ASCertificateStorage.h"

#define SESSION_CACHE_PATH [NSHomeDirectory() stringByAppendingPathComponent:@".xauth/sessions"]



@interface ASAnisetteDataProvider (SessionManager) <ASSessionManagerAnisetteDataProvider> @end
@implementation ASAnisetteDataProvider (SessionManager)
- (void)sessionManager:(ASSessionManager *)sessionManager generateAnisetteData:(void (^)(ALTAnisetteData *, NSError *))handler {
    [self fetchAnisetteData:handler];
}
@end

@interface ASCertificateStorage (SessionManager) <ASSessionManagerCertificateStorage> @end
@implementation ASCertificateStorage (SessionManager)
- (void)sessionManager:(ASSessionManager *)sessionManager writeCertificateData:(NSData *)data forKey:(NSString *)key {
    [self setObject:data forKeyedSubscript:key];
}
- (NSData *)sessionManager:(ASSessionManager *)sessionManager readCertificateDataForKey:(NSString *)key {
    return [self objectForKeyedSubscript:key];
}
@end





@interface ASSessionManager ()

@property (nonatomic, strong, readonly) NSMutableDictionary<NSString *, ASAccountSession *> *mAccountSessions;

#if TARGET_OS_OSX
@property (nonatomic, strong, readonly) ASAnisetteDataLocalProvider *localProvider;
#endif

@end

@interface ASSessionManager (PrivateDelegate) <ASSessionPrivateDelegate>
@end

@implementation ASSessionManager

- (instancetype)init {
    self = [super init];
    if (self) {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            OpenSSL_add_all_algorithms();
        });
        _certificateStorage = [[ASCertificateStorage alloc] init];
        _mAccountSessions = [NSMutableDictionary dictionary];
        [self __readSession];
    }
    return self;
}

- (void)__readSession {
    NSData *data = [NSData dataWithContentsOfFile:SESSION_CACHE_PATH];
    NSArray<ASAccountSession *> *list = [NSKeyedUnarchiver unarchivedArrayOfObjectsOfClass:[ASAccountSession class] fromData:data error:nil];
    for (ASAccountSession *item in list) {
        item.privateDelegate = self;
        self.mAccountSessions[item.account.appleID] = item;
    }
}

- (void)__saveSession {
    NSArray<ASAccountSession *> *sessions = self.mAccountSessions.allValues;
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:sessions requiringSecureCoding:NO error:NULL];
    [data writeToFile:SESSION_CACHE_PATH atomically:YES];
}

- (void)login:(NSString *)AppleID password:(NSString *)password verify:(void (^)(void (^)(NSString *)))verify completed:(void (^)(ASAccountSession *, NSError *))completion {
    
    __auto_type GenNewSession = ^{
        [self __generateAnisetteData:^(ALTAnisetteData *data, NSError *error) {
            if (error) {
                !completion ?: completion(nil, error);
                return;
            }
            
            [[ALTAppleAPI sharedAPI] authenticateWithAppleID:AppleID
                                                    password:password
                                                anisetteData:data
                                         verificationHandler:verify
                                           completionHandler:^(ALTAccount * _Nullable account, ALTAppleAPISession * _Nullable session, NSError * _Nullable error) {
                if (error) {
                    !completion ?: completion(nil, error);
                    return;
                }
                
                ASAccountSession *accountSession = [[ASAccountSession alloc] initWithSession:session account:account];
                accountSession.privateDelegate = self;
                self.mAccountSessions[account.appleID] = accountSession;
                [self __saveSession];
                !completion ?: completion(accountSession, nil);
            }];
        }];
    };
    
    ASAccountSession *session = self.mAccountSessions[AppleID];
    if (session) {
        [session fetchTeams:^(NSArray<ALTTeam *> *teams, NSError *error) {
            if (error) {
                GenNewSession();
            } else {
                !completion ?: completion(session, nil);
            }
        }];
    } else {
        GenNewSession();
    }
}

- (void)logout:(ASAccountSession *)session {
    NSString *appleID = session.account.appleID;
    if (!appleID) {
        return;
    }
    ASAccountSession *cachedSession = self.mAccountSessions[appleID];
    if (cachedSession != session) {
        return;
    }
    
    self.mAccountSessions[appleID] = nil;
    [self __saveSession];
}

- (NSArray<ASAccountSession *> *)sessions {
    return self.mAccountSessions.allValues;
}

- (void)__generateAnisetteData:(void (^)(ALTAnisetteData *data, NSError *error))completion {
    if ([self.anisetteDataProvider respondsToSelector:@selector(sessionManager:generateAnisetteData:)]) {
        [self.anisetteDataProvider sessionManager:self generateAnisetteData:completion];
        return;
    }
#if TARGET_OS_OSX
    [self.localProvider fetchAnisetteData:completion];
#else
    NSError *error = [NSError errorWithDomain:ASErrorDomain code:ASErrorNoAnisetteDataProvider userInfo:@{
        NSLocalizedDescriptionKey: @"Login failed",
        NSLocalizedFailureReasonErrorKey: @"Can not create AnisetteData",
        NSLocalizedRecoverySuggestionErrorKey: @"Use -[ASSessionManager setAnisetteDataProvider:] to provide it.",
    }];
    completion(nil, error);
#endif
}

#if TARGET_OS_OSX
@synthesize localProvider = _localProvider;
- (ASAnisetteDataLocalProvider *)localProvider {
    if (!_localProvider) {
        _localProvider = [[ASAnisetteDataLocalProvider alloc] init];
    }
    return _localProvider;
}
#endif

@end

@implementation ASSessionManager (PrivateDelegate)

- (NSData *)certificateDataForKey:(NSString *)key {
    return [self.certificateStorage sessionManager:self readCertificateDataForKey:key];
}

- (void)setCertificateData:(NSData *)data forKey:(NSString *)key {
    [self.certificateStorage sessionManager:self writeCertificateData:data forKey:key];
}

@end
